﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Resources;
using System.IO;
using System.Collections;
using System.Xml;
using System.Reflection;
using System.Threading;
using Microsoft.Win32;
using System.Diagnostics;

namespace Deployment.BuildManager.ExternalProcess.CreateSetupFile
{
    /// <summary>
    /// Interaction logic for Setup.xaml
    /// </summary>
    public partial class Setup : Window
    {
        #region [ Const ]
        private const string SetupDownloadFolder = "bin";
        private const string SetupExeFileName = "SetupConsole.exe";
        private const string SetupXmlFileName = "SetupConfiguration.xml";
        private const string SourceFilesResourceName = "SourceFiles.resx";

        private const string XmlExtractDocResourceElement = "Resource";
        private const string XmlExtractDocResourceNameAttribute = "name";
        private const string XmlExtractDocExtractPathAttribute = "extractPath";
        private const string XmlExtractResourceKey = "Resources";

        private const string XmlAttributeSystemName = "System";
        private const string XmlAttributeSystemVersion = "Version";
        private const string XmlAttributeType = "Type";
        private const string XmlTypeHotFixValue = "HotFix";
        private const string XmlTypeServicePackValue = "ServicePack";

        private const string ResourcesPrefix = "Deployment.BuildManager.ExternalProcess.CreateSetupFile.Resources.";
        private const string XmlResourceName = "Resources.xml";

        private const string HotFixCommand = "/hf";
        private const string ServicePackCommand = "/sp";
        #endregion [ Const ]

        private string _xmlSystemNameValue = String.Empty;
        private string _xmlSystemVersionValue = String.Empty;
        private string _xmlTypeValue = String.Empty;
        private string _installPath = String.Empty;
        private List<string> _copiedFiles = new List<string>();
        private SetupDetails _setupDetails = new SetupDetails();

        private string _SystemAndVersion = String.Empty;

        private int _resourceCount = 0;

        public string AdditionalCommandParameters { get; set; }

        public int ExitCode { get; private set; }

        public Setup()
        {
            InitializeComponent();

            //recuperar as informações do sistema antes de iniciar
            RetrieveSystemInformation();

            this.Loaded += new RoutedEventHandler(Window_Loaded);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            FileNameLabel.Text = "";
            PercentLabel.Text = "0%";

            Thread thread = new Thread(new ThreadStart(Start));
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }

        internal void Start()
        {
            try
            {
                this.ExtractFiles();
                this.StartSetupProcess();
                this.CloseApp();
            }
            catch (Exception ex)
            {
                ShowMessage(ex.Message);
                this.CloseApp();
            }
        }

        internal bool ValidateInstalledSystem(out string message)
        {
            bool validate = false;
            message = string.Empty;
            try
            {
                string path = GetInstallationPath();
                validate = !string.IsNullOrEmpty(path);
            }
            catch (SetupException ex)
            {
                message = ex.Message;
                validate = false;
            }
            return validate;
        }

        internal string GetInstallationPath()
        {
            string path = String.Empty;

            RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + _SystemAndVersion);

            if (key == null)
            {
                throw new SetupException("Para instalar esta atualização é necessário que o sistema " + _xmlSystemNameValue + " esteja instalado.");
            }
            else
            {
                try
                {
                    path = key.GetValue("InstallLocation").ToString();
                    path = System.IO.Path.Combine(path, _xmlSystemNameValue);
                    path = System.IO.Path.Combine(path, string.Format("{0}-v{1}", "Setup", _xmlSystemVersionValue));
                }
                catch
                {
                    throw new SetupException("As configurações de instalação do sistema " + _xmlSystemNameValue + " estão corrompidas. Por favor entre em contato com o administrador.");
                }
            }

            return path;
        }

        public void ShowMessage(string message)
        {
            Window.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        MessageBox.Show(message, "Setup", MessageBoxButton.OK);
                    }
                ));
        }

        /// <summary> 
        /// Copies the contents of input to output. Doesn't close either stream. 
        /// </summary> 
        internal void CopyStream(Stream input, Stream output)
        {
            byte[] buffer = new byte[8 * 1024];
            int len;
            while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, len);
            }
        }

        void ChangeTitle()
        {
            this.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        Window.Title = "Setup " + _xmlSystemNameValue;
                    }
                ));
        }

        void IncrementProgressBar()
        {
            ProgressBar1.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        ProgressBar1.Value++;
                        int percent = (int)((ProgressBar1.Value * 100) / ProgressBar1.Maximum);
                        PercentLabel.Text = percent.ToString() + "%";
                    }
                ));
        }

        void CloseApp()
        {
            this.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        this.Close();
                    }
                ));
        }

        void UpdateProgressBarMaximum(int value)
        {
            ProgressBar1.Dispatcher.Invoke(
                new Action(
                    delegate()
                    {
                        ProgressBar1.Maximum = value;
                    }
                ));
        }

        void ChangeFileLabel(string value)
        {
            FileNameLabel.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        FileNameLabel.Text = "Arquivo: " + value;
                    }
                ));
        }

        internal void RetrieveSystemInformation()
        {
            try
            {
                XmlDocument document;

                Assembly assembly = Assembly.GetExecutingAssembly();

                // Get the embedded XML file that contains information of the other embedded files.
                using (Stream stream = assembly.GetManifestResourceStream(ResourcesPrefix + XmlResourceName))
                {
                    document = new XmlDocument();
                    document.Load(stream);
                }

                XmlNode node = document.SelectSingleNode("Resources");
                // The number of embedded resources to copy.
                _xmlSystemNameValue = node.Attributes[XmlAttributeSystemName].Value;
                _xmlSystemVersionValue = node.Attributes[XmlAttributeSystemVersion].Value;
                _SystemAndVersion = string.Format("{0}-v{1}", _xmlSystemNameValue, _xmlSystemVersionValue);

                if (string.IsNullOrEmpty(_xmlSystemNameValue) || string.IsNullOrEmpty(_xmlSystemVersionValue))
                    throw new SetupException("Não foi possível obter as informações sobre o sistema a ser instalado");

                if (node.Attributes[XmlAttributeType].Value.Equals(XmlTypeHotFixValue))
                {
                    _xmlTypeValue = HotFixCommand;
                }
                else if (node.Attributes[XmlAttributeType].Value.Equals(XmlTypeServicePackValue))
                {
                    _xmlTypeValue = ServicePackCommand;
                }
                else
                {
                    throw new SetupException("Arquivo de instalação corrompido.");
                }

                _resourceCount = node.ChildNodes.Count;
            }
            catch (SetupException ex)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new SetupException("Não foi possível obter as informações sobre o sistema a ser instalado", ex);
            }
        }

        internal void ExtractFiles()
        {
            XmlDocument document;

            Assembly assembly = Assembly.GetExecutingAssembly();

            // Get the embedded XML file that contains information of the other embedded files.
            using (Stream stream = assembly.GetManifestResourceStream(ResourcesPrefix + XmlResourceName))
            {
                document = new XmlDocument();
                document.Load(stream);
            }

            //XmlNode node = document.SelectSingleNode("Resources");

            //// The number of embedded resources to copy.
            //_xmlSystemNameValue = node.Attributes[XmlAttributeSystemName].Value;
            //_xmlSystemVersionValue = node.Attributes[XmlAttributeSystemVersion].Value;
            //_SystemAndVersion = string.Format("{0}-v{1}", _xmlSystemNameValue, _xmlSystemVersionValue);

            //if (string.IsNullOrEmpty(_xmlSystemNameValue) || string.IsNullOrEmpty(_xmlSystemVersionValue))
            //    throw new SetupException("Não foi possível obter as informações sobre o sistema a ser instalado");
            //if (node.Attributes[XmlAttributeType].Value.Equals(XmlTypeHotFixValue))
            //{
            //    _xmlTypeValue = HotFixCommand;
            //}
            //else if (node.Attributes[XmlAttributeType].Value.Equals(XmlTypeServicePackValue))
            //{
            //    _xmlTypeValue = ServicePackCommand;
            //}
            //else
            //{
            //    throw new SetupException("Arquivo de instalação corrompido.");
            //}

            ChangeTitle();

            //int count = node.ChildNodes.Count;

            this.UpdateProgressBarMaximum(_resourceCount);

            XmlNodeReader nodeReader = new XmlNodeReader(document);
            XmlReader reader = XmlReader.Create(nodeReader, null);

            // Get the installation path of the Setup.exe. 
            // The files will be copied to the bin directory of the Setup folder.
            _installPath = GetInstallationPath();

            string name;
            string extractPath;

            while (reader.Read())
            {
                if (reader.LocalName.Equals(XmlExtractDocResourceElement))
                {
                    name = reader.GetAttribute(XmlExtractDocResourceNameAttribute);
                    extractPath = reader.GetAttribute(XmlExtractDocExtractPathAttribute);

                    // Get the embedded resource and saves it to a file.
                    using (Stream resourceStream = assembly.GetManifestResourceStream(ResourcesPrefix + name))
                    {
                        string fileName = System.IO.Path.Combine(_installPath, extractPath);
                        this.ChangeFileLabel(fileName);
                        string directoryName = System.IO.Path.GetDirectoryName(fileName);
                        if (!Directory.Exists(directoryName))
                        {
                            Directory.CreateDirectory(directoryName);
                        }
                        using (Stream file = System.IO.File.OpenWrite(fileName))
                        {
                            CopyStream(resourceStream, file);
                            _copiedFiles.Add(fileName);
                        }
                    }
                    this.IncrementProgressBar();
                    //Thread.Sleep(500);
                }
            }
        }

        private void StartSetupProcess()
        {
            string installPath = GetInstallationPath();

            string xmlSetupPath = System.IO.Path.Combine(installPath, SetupDownloadFolder);
            xmlSetupPath = System.IO.Path.Combine(xmlSetupPath, SetupXmlFileName);

            System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();

            startInfo.FileName = System.IO.Path.Combine(installPath, SetupExeFileName);

            if (!File.Exists(startInfo.FileName))
                throw new SetupException(string.Format("Não foi possível encontrar o executável do setup no caminho '{0}'", startInfo.FileName));

            // Xml Config file.
            startInfo.Arguments = _xmlTypeValue + " /filename=\"" + xmlSetupPath + "\" " + this.AdditionalCommandParameters;

            startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardError = true;
            startInfo.RedirectStandardOutput = true;

            Process.Instance.Finalized += new Action<int>(Process_Finalized);
            Process.Instance.Started += new Action(Process_Started);

            Process.Instance.StartSetupProcess(startInfo);
        }

        private void Process_Started()
        {
            if (System.Threading.Thread.CurrentThread == this.Dispatcher.Thread)
            {
                if (!string.IsNullOrEmpty(this.AdditionalCommandParameters))
                {
                    _setupDetails.Show();
                }
            }
            else
            {
                this.Dispatcher.Invoke(new Action(Process_Started));
            }
        }

        private void CloseSetupDetails()
        {
            if (System.Threading.Thread.CurrentThread == this.Dispatcher.Thread)
            {
                if (!string.IsNullOrEmpty(this.AdditionalCommandParameters))
                {
                    _setupDetails.Close();
                }
            }
            else
            {
                this.Dispatcher.Invoke(new Action(CloseSetupDetails));
            }
        }

        private void Process_Finalized(int exitCode)
        {
            try
            {
                CloseSetupDetails();

                if (exitCode == -1)
                {
                    MessageBox.Show("Ocorreu um erro crítico e o Setup será fechado, verifique o Event Viewer para maiores detalhes.", "Erro", MessageBoxButton.OK, MessageBoxImage.Error);

                    this.ExitCode = exitCode;
                }

                foreach (string file in _copiedFiles)
                {
                    if (File.Exists(file) && !file.Contains("Deployment"))
                        File.Delete(file);

                    if (Directory.Exists(System.IO.Path.GetDirectoryName(file)))
                        DeleteEmptyDirectories(_installPath, System.IO.Path.GetDirectoryName(file));
                }
            }
            catch { }
        }

        public void DeleteEmptyDirectories(string basePath, string directory)
        {
            if (basePath.EndsWith("\\"))
                basePath = basePath.TrimEnd('\\');

            if (directory.EndsWith("\\"))
                directory = directory.TrimEnd('\\');

            if (Directory.GetFiles(directory).Length == 0 &&
                Directory.GetDirectories(directory).Length == 0)
            {
                string parent = Directory.GetParent(directory).FullName;
                Directory.Delete(directory);

                if (directory != basePath)
                    DeleteEmptyDirectories(basePath, parent);
            }
        }
    }
}